agentmux_srv\backend\blockcontroller/process_tree.rs
1// Copyright 2025-2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Process tree traversal: BFS from a root PID to collect all descendant PIDs.
5//! Used by the sysinfo loop to aggregate CPU/memory across entire process trees.
6
7use std::collections::{HashMap, VecDeque};
8
9use sysinfo::{Pid, System};
10
11/// Maximum number of PIDs to track per block (safety cap against pathological trees).
12pub const MAX_PIDS_PER_BLOCK: usize = 64;
13
14/// Returns the root PID plus all descendant PIDs via BFS, capped at `max_pids`.
15///
16/// Requires `sys` to have been refreshed with at least a minimal `ProcessRefreshKind`
17/// (so that `Process::parent()` is populated for all processes).
18pub fn collect_descendants(sys: &System, root: Pid, max_pids: usize) -> Vec<Pid> {
19 // Build parent → children adjacency map in O(N) over all known processes.
20 let mut children: HashMap<Pid, Vec<Pid>> = HashMap::new();
21 for (pid, proc) in sys.processes() {
22 if let Some(ppid) = proc.parent() {
23 children.entry(ppid).or_default().push(*pid);
24 }
25 }
26
27 // BFS from root, stopping when we hit max_pids.
28 let mut result = Vec::with_capacity(max_pids.min(8));
29 result.push(root);
30 let mut queue = VecDeque::from([root]);
31
32 while let Some(pid) = queue.pop_front() {
33 if result.len() >= max_pids {
34 break;
35 }
36 if let Some(kids) = children.get(&pid) {
37 for &child in kids {
38 if result.len() >= max_pids {
39 break;
40 }
41 result.push(child);
42 queue.push_back(child);
43 }
44 }
45 }
46
47 result
48}